/*
 * Copyright (c) 2008-2016, RF-Embedded GmbH
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 *  1. Redistributions of source code must retain the above copyright notice,
 *     this list of conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright notice,
 *     this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
 * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "TestProgram.h"

#include <helper/StringHelper.h>
#include <helper/Sleeper.h>

#include "impl/QtDevice.h"
#include "impl/ConsoleTrace.h"
#include "impl/Timer.h"


#include <iostream>
#include <set>

#include <QTcpSocket>
#include <QHostAddress>
#include <QSerialPort>
#include <QTime>

using namespace CPPrfeReaderInterface;


// ************************************************* Change settings here
#define TEST_PROGRAM_USE_TCP        0
#define TEST_PROGRAM_TCP_IP         "127.0.0.1"
#define TEST_PROGRAM_TCP_PORT       52475
#define TEST_PROGRAM_SERIAL_PORT    "COM3"

TestProgram::TestProgram()
{
    m_ph = 0;
}

TestProgram::~TestProgram()
{
    if(m_ph)
        delete m_ph;

    if(Global::m_tracer)
        delete Global::m_tracer;
}

bool TestProgram::init()
{
    // create trace and turn off
    Global::m_tracer = new ConsoleTrace();
    Global::m_tracer->setTraceLevel(0);

    /*********************************************************************************/// QT BEGIN
#if TEST_PROGRAM_USE_TCP
    QString ip = TEST_PROGRAM_TCP_IP;
    ushort port = TEST_PROGRAM_TCP_PORT;
    QTcpSocket* dev = new QTcpSocket();
    dev->connectToHost(QHostAddress(ip), port);
    if(!dev->waitForConnected())
    {
        print("Could not connect to " + ip.toStdString() + ":" + StringHelper::toIntString(port));
        delete dev;
        return false;
    }
#else
    // Open serial port
    QSerialPort* dev = new QSerialPort();
    dev->setPortName(TEST_PROGRAM_SERIAL_PORT);
    dev->setFlowControl(QSerialPort::NoFlowControl);
    dev->setParity(QSerialPort::NoParity);
    dev->setDataBits(QSerialPort::Data8);
    dev->setStopBits(QSerialPort::OneStop);
    dev->setBaudRate(QSerialPort::Baud115200);
    if(!dev->open(QIODevice::ReadWrite))
    {
        print("Could not open the serial port " + std::string(TEST_PROGRAM_SERIAL_PORT));
        delete dev;
        return false;
    }
#endif

    QtDevice* device = new QtDevice(dev);
    if(!device->open())
    {
        print("Could not open socket...");
        delete device;
        return false;
    }

    m_ph = new CPPrfeProtocolHandler(device);
    m_ph->setEventListener(this);
    /*********************************************************************************/// QT END

    return true;
}


void TestProgram::print(const char* str)
{
   std::cout << str << std::endl;
}

void TestProgram::print(const std::string& str)
{
    std::cout << str << std::endl;
}


void TestProgram::printMenu()
{
    if (system("CLS")) system("clear");
    print("+------------------------ MENU ----------------------------+");
    print("|    1 = Query Reader Information                          |");
    print("|    2 = Test Attenuation Settings                         |");
    print("|    3 = Read Frequency Settings                           |");
    print("|    4 = Read Sensitivity Settings                         |");
    print("|    5 = Test Heartbeat                                    |");
    print("|    6 = Test GPIO                                         |");
    print("|    8 = Start Inventory                                   |");
    print("|    9 = Do SinglInventory                                 |");
    print("|   23 = Try to Read TID                                   |");
    print("|   24 = Try to Read/Write User Mem                        |");
    print("|   31 = Test Read TID of First Tag - Slow                 |");
    print("+----------------------------------------------------------+");
    print("");
    print("Please choose: > ");
}

void TestProgram::executeTest(std::string str)
{
    int value = atoi(str.c_str());

    switch(value)
    {
    default:
    case 1:
        test_ReaderInfo();
        break;
    case 2:
        test_Attenuation();
        break;
    case 3:
        test_Frequency();
        break;
    case 4:
        test_Sensitivity();
        break;
    case 5:
        test_Heartbeat();
        break;
    case 6:
        test_GPIO();
        break;
    case 8:
        test_Inventory();
        break;
    case 9:
        test_SingleInventory();
        break;

    case 23:
        test_ReadTID();
        break;
    case 24:
        test_ReadWriteUser();
        break;

    case 31:
        test_AN001_ReadTIDFirstTag_Slow();
        break;
    }

    system("pause");
}

int TestProgram::getTagId(std::vector<CPPrfeReaderInterface::CPPrfeTagEvent> tagIds)
{
    print("Tag List:");
    for(uint i = 0; i < tagIds.size(); i++)
    {
        print("   " + StringHelper::toIntString(i, 2) + " - " + StringHelper::toString(tagIds.at(i).tagId));
    }
    print("Select tag:");

    std::string str;
    std::cin >> str;

    if(str.at(0) == 'q')
        return -1;

    int value = atoi(str.c_str());
    if(value < 0 || value >= (int)tagIds.size())
        return -1;

    return value;
}




void TestProgram::test_ReaderInfo()
{
    bool ok = false;

    // get reader id
    ulong readerId = 0;
    ok = m_ph->getReaderID(readerId);
    if (!ok)
        print("ERROR: Could not get ReaderID");

    // get reader type
    ulong readerType = 0;
    ok = m_ph->getReaderType(readerType);
    if (!ok)
        print("ERROR: Could not get ReaderType");

    // get hardware revision
    ulong hwRev = 0;
    ok = m_ph->getHardwareRevision(hwRev);
    if (!ok)
        print("ERROR: Could not get HardwareRevision");

    // get software revision
    ulong swRev = 0;
    ok = m_ph->getSoftwareRevision(swRev);
    if (!ok)
        print("ERROR: Could not get SoftwareRevision");

    // get bootloader revision
    ulong blRev = 0;
    ok = m_ph->getBootloaderRevision(blRev);
    if (!ok)
        print("ERROR: Could not get BootloaderRevision");

    // get current system
    eRFE_CURRENT_SYSTEM system;
    ok = m_ph->getCurrentSystem(system);
    if (!ok)
        print("ERROR: Could not get CurrentSystem");

    // get current state
    eRFE_CURRENT_READER_STATE state;
    ok = m_ph->getCurrentState(state);
    if (!ok)
        print("ERROR: Could not get CurrentState");

    // get status register
    ulonglong statusReg = 0;
    ok = m_ph->getStatusRegister(statusReg);
    if (!ok)
        print("ERROR: Could not get StatusRegister");

    // print out results
    print("Reader Information:");
    print("\t -> ReaderID       = " + StringHelper::toHexString(readerId));
    print("\t -> ReaderType     = " + StringHelper::toHexString(readerType));
    print("\t -> HardwareRev    = " + StringHelper::toHexString(hwRev));
    print("\t -> SoftwareRev    = " + StringHelper::toHexString(swRev));
    print("\t -> BootloaderRev  = " + StringHelper::toHexString(blRev));
    print("\t -> Current System = " + StringHelper::toString(system));
    print("\t -> Current State  = " + StringHelper::toString(state));
    print("\t -> StatusRegister = " + StringHelper::toHexString(statusReg));
}

void TestProgram::test_Attenuation()
{
    print("Testing attenuation settings:");

    ushort attMax, attCur, attTemp;

    // get attenuation values
    print("\t -> 1, Reading attenuation settings:");
    m_ph->getAttenuation(attMax, attCur);
    print("\t\t Attenuation Max= " + StringHelper::toIntString(attMax) + "Current=" + StringHelper::toIntString(attCur));
    print("");

    // set attenuation to fix value 10
    print("\t -> 2, Setting attenuation settings:");
    m_ph->setAttenuation(10);
    print("\t\t Set Attenuation to 10");
    print("");

    // get attenuation settings and check if the fix value is set
    print("\t -> 3, Reading attenuation settings:");
    m_ph->getAttenuation(attMax, attTemp);
    print("\t\t Attenuation Max=" + StringHelper::toIntString(attMax) + "Current=" + StringHelper::toIntString(attTemp));
    print("\t\t Current attenuation " + StringHelper::toIntString(attTemp) + " == 10?");
    if (attTemp != 10)
    {
        print("ERROR------------------ Set Attenuation is not the Current Attenuation");
        return;
    }
    print("\t\t OK\n");

    // retore attenuation to the previous value
    print("\t -> 4, Restore attenuation settings:");
    m_ph->setAttenuation(attCur);
    print("\t\t Set Attenuation to 0");
    print("");

    // check the set values again
    print("\t -> 5, Reading attenuation settings:");
    m_ph->getAttenuation(attMax, attCur);
    print("\t\t Attenuation Max=" + StringHelper::toIntString(attMax) + "Current=" + StringHelper::toIntString(attCur));
    print("");
}

void TestProgram::test_Frequency()
{
    print("Testing frequency settings:");

    uchar mode, maxCount;
    std::vector<uint> frequ;
    std::vector<uint> tempFrequ;

    // get current frequency table
    print("\t -> 1, Reading frequency settings:");
    m_ph->getFrequency(mode, maxCount, frequ);
    print("\t\t FrequencyTable Mode=" + StringHelper::toIntString(mode) + " Max=" + StringHelper::toIntString(maxCount) + " Current=" + StringHelper::toIntString(frequ.size()));
    for (uint i = 0; i < frequ.size(); i++)
    {
        print("\t\t\t " + StringHelper::toIntString(i) + " = " + StringHelper::toIntString(frequ[i]));
    }
    print("");
}

void TestProgram::test_Sensitivity()
{
    print("Testing sensitivity settings:");

    short maxSens, minSens, curSens, temSens, actSens;

    // get current sensitivity
    print("\t -> 1, Reading sensitivity settings:");
    m_ph->getSensitivity( maxSens,  minSens,  curSens);
    print("\t\t Sensitivity Max=" + StringHelper::toIntString(maxSens) + " Min=" + StringHelper::toIntString(minSens) + " Current=" + StringHelper::toIntString(curSens));
    print("");
}

void TestProgram::test_Heartbeat()
{

    print("Testing Heartbeat:");

    // turn on heartbeat with an interval of 250ms
    print("\t -> 1, Setting Heartbeat ON with interval 250ms");
    m_ph->setHeartBeat(CPPrfeReaderInterface::HEARTBEAT_ON, 250);

    print("");
    uint waitTime = 10000;
    Sleeper::msleep(waitTime);

    // turn off heartbeat
    print("\t -> 2, Setting Heartbeat OFF");
    m_ph->setHeartBeat(CPPrfeReaderInterface::HEARTBEAT_OFF, 250);
}

void TestProgram::test_GPIO()
{
    print("Test GPIO - Output");

    ulong mask, output, input;
    if (!m_ph->getGPIOCaps(mask, output, input))
    {
        print("ERROR: Could not get GPIO Caps");
        return;
    }

    print("Mask: " + StringHelper::toHexString(mask));
    print("Output: " + StringHelper::toHexString(output));
    print("Input: " + StringHelper::toHexString(input));
}

void TestProgram::test_Inventory()
{
    print("Testing Cyclic Inventory:");

//    Power Safe
//    std::vector<byte> payload;
//    payload.resize(3);
//    payload[0] = (byte) 0x01;
//    payload[1] = (byte) 0x00;
//    payload[2] = (byte) 0xFA;
//    m_ph->setParam(0x0001, payload);

    // turn on cyclic inventory
    print("\t -> 1, Starting Cyclic Inventory");
    m_cyclicInvCount = 0;
    m_ph->setCyclicInventory(true);

    print("");
    uint waitTime = 10000;
    Sleeper::msleep(waitTime);

    // turn off cyclic inventory and calculate read rate
    m_ph->setCyclicInventory(false);
    double readRate = (double)m_cyclicInvCount / ((double)waitTime/1000.0);
    print("\t -> 2, Stopped Cyclic Inventry with a ReadRate of " + StringHelper::toIntString(readRate) + " reads/sec");
}

void TestProgram::test_SingleInventory()
{
    // do a single inventory and print out the list
    print("Do SingleInventory");
    std::vector<CPPrfeReaderInterface::CPPrfeTagEvent> tagList;
    m_ph->doSingleInventory(tagList);
    print("\t\t -> Found " + StringHelper::toIntString(tagList.size()) + " Tags");

    for(uint i = 0; i < tagList.size(); i++)
    {
        CPPrfeReaderInterface::CPPrfeTagEvent& tag = tagList.at(i);
        print("\t\t #" + StringHelper::toIntString(i, 2) + " " + tag.toString());
    }
}


void TestProgram::test_ReadTID()
{
    print("Trying to read tid register:");

    // do a single inventory and print out the tag list with an index to select one tag
    print("\t -> 1, Searching for tags:");
    std::vector<CPPrfeReaderInterface::CPPrfeTagEvent> tagList;
    m_ph->doSingleInventory(tagList);
    print("\t\t -> Found " + StringHelper::toIntString(tagList.size()) + " Tags");

    print("");
    print("\t -> 2, Select a tag by index: > ");
    std::vector<byte> selectedEpc;
    if (tagList.size() > 0)
    {
        int index = getTagId(tagList);
        if(index < 0)
            return;

        selectedEpc = tagList.at(index).tagId;
    }
    else
    {
        print("\t\t Found 0 Tags, stopping the test...");
        return;
    }

    print("");
    // try to read the first 4 bytes of the TID membank of the selected tag
    print("\t -> 3, Trying to read first 4 bytes of TID register:");
    std::vector<byte> passwd(4, (byte)0);
    std::vector<byte> data;
    if (m_ph->readFromTag(selectedEpc, 0x02, 0, passwd, 4, data))
    {
        print("\t\t Read succeeded, read data: " + StringHelper::toString(data));
    }
    else
    {
        print("\t\t Read failed:" + StringHelper::toString(m_ph->lastReturnCode()));
    }
    print("");
}

void TestProgram::test_ReadWriteUser()
{
    print("Trying to read write user register:");

    // do a single inventory and print out the tag list with an index to select one tag
    print("\t -> 1, Searching for tags:");
    std::vector<CPPrfeReaderInterface::CPPrfeTagEvent> tagList;
    m_ph->doSingleInventory(tagList);
    print("\t\t -> Found " + StringHelper::toIntString(tagList.size()) + " Tags");

    print("");
    print("\t -> 2, Select a tag by index: > ");
    std::vector<byte> selectedEpc;
    if (tagList.size() > 0)
    {
        int index = getTagId(tagList);
        if(index < 0)
            return;

        selectedEpc = tagList.at(index).tagId;
    }
    else
    {
        print("\t\t Found 0 Tags, stopping the test...");
        return;
    }

    uchar memBank = 0x03;
    std::vector<byte> passwd(4, (byte)0);
    std::vector<byte> userMemBefore;
    std::vector<byte> userMemAfter;
    std::vector<byte> old4Bytes;
    std::vector<byte> new4Bytes;

    new4Bytes.push_back(0x11);
    new4Bytes.push_back(0x22);
    new4Bytes.push_back(0x33);
    new4Bytes.push_back(0x44);

    // try to read the whole user memory bank
    print("\t -> 3, Trying to Read 4 bytes from user mem");
    if (m_ph->readFromTag(selectedEpc, memBank, 0, passwd, 4, userMemBefore))
    {
        print("\t\t Read succeeded, read data:");
        std::string mem;
        for (uint i = 0; i < userMemBefore.size(); i++)
        {
            mem += StringHelper::toHexString(userMemBefore[i]);
            mem += "-";
            if(((i + 1)%8) == 0)
                mem += ("\n\t\t\t ");
        }
        print("\t\t\t " + mem);
        print("\n");
    }
    else
    {
        print("\t\t Read failed:" + StringHelper::toString(m_ph->lastReturnCode()));
        return;
    }
    print("");

    // save the old first 4 bytes of the user memory
    old4Bytes = std::vector<byte>(userMemBefore.begin(), userMemBefore.begin()+4);

    // try to write 4 dummy bytes to the user mem
    print("\t -> 4, Trying to Wrtie data:");
    print("\t\t\t " + StringHelper::toString(new4Bytes));
    if (m_ph->writeToTag(selectedEpc, memBank, 0, passwd, new4Bytes))
    {
        print("\t\t Wrtie succeeded");
    }
    else
    {
        print("\t\t Write failed:" + StringHelper::toString(m_ph->lastReturnCode()));
        return;
    }
    print("");

    // try to read the whole user me again
    print("\t -> 5, Trying to Read written data");
    if (m_ph->readFromTag(selectedEpc, memBank, 0, passwd, 4, userMemAfter))
    {
        print("\t\t Read succeeded, read data:");
        std::string mem;
        for (uint i = 0; i < userMemAfter.size(); i++)
        {
            mem += StringHelper::toHexString(userMemAfter[i]);
            mem += "-";
            if(((i + 1)%8) == 0)
                mem += ("\n\t\t\t ");
        }
        print("\t\t\t " + mem);
        print("\n");
    }
    else
    {
        print("\t\t Read failed:" + StringHelper::toString(m_ph->lastReturnCode()));
        return;
    }
    print("");

    // try to write the old bytes into the memory
    print("\t -> 6, Trying to restore the bytes");
    if (m_ph->writeToTag(selectedEpc, memBank, 0, passwd, old4Bytes))
    {
        print("\t\t Wrtie succeeded");
    }
    else
    {
        print("\t\t Write failed:" + StringHelper::toString(m_ph->lastReturnCode()));
        return;
    }
    print("");

    // again read the restored bytes from the tag
    print("\t -> 7, Trying to Read written data");
    if (m_ph->readFromTag(selectedEpc, memBank, 0, passwd, 4, userMemAfter))
    {
        print("\t\t Read succeeded, read data:");
        std::string mem;
        for (uint i = 0; i < userMemAfter.size(); i++)
        {
            mem += StringHelper::toHexString(userMemAfter[i]);
            mem += "-";
            if(((i + 1)%8) == 0)
                mem += ("\n\t\t\t ");
        }
        print("\t\t\t " + mem);
        print("\n");
    }
    else
    {
        print("\t\t Read failed:" + StringHelper::toString(m_ph->lastReturnCode()));
        return;
    }
    print("");
}

void TestProgram::test_AN001_ReadTIDFirstTag_Slow()
{
    print("Trying to read tid register:");


    print("\t -> 1, Searching for tags:");

    m_cyclicInvCount = 0;
    m_lastTagEventValid = false;
    Timer startTime;

    m_ph->setCyclicInventory(true);
    bool res = waitForCyclicInvResult(5000);
    m_ph->setCyclicInventory(false);
    startTime.reset();

    if(!res)
    {
        print("\t\t Did not find any tag...");
        return;
    }

    print("\t\t Found Tag " + StringHelper::toString(m_lastTagEvent.tagId));

    // try to read the first 4 bytes of the TID membank of the detected tag
    print("\t -> 2, Trying to read first 4 bytes of TID register:");
    std::vector<byte>  passwd(4, (byte)0);
    std::vector<byte>  data;
    if (m_ph->readFromTag(m_lastTagEvent.tagId, 0x02, 0, passwd, 4, data))
    {
        print("\t\t Read succeeded, read data: " + StringHelper::toString(data));
    }
    else
    {
        print("\t\t Read failed:" + StringHelper::toString(m_ph->lastReturnCode()));
    }
    print("");
    print("Read Result " + StringHelper::toIntString(startTime.elapsed()) + "ms after the tag was detected");
    print("");
}


bool TestProgram::waitForCyclicInvResult(int waitTime)
{
    Timer t;

    while(! (t.elapsed() > waitTime) )
    {
        if(m_lastTagEventValid)
            return true;

        Sleeper::msleep(1);
    }

    return false;
}




void TestProgram::HeartBeatHandler(const std::vector<byte> &data)
{
    print("\t[E] Heartbeat " + StringHelper::toString(data));
}

void TestProgram::CyclicInventoryHandler(const CPPrfeTagEvent &tagInfo)
{
    if(!m_lastTagEventValid)
    {
        m_lastTagEventValid = true;
        m_lastTagEvent = tagInfo;
    }

    std::string info;
    info = "\t[E] " + StringHelper::toIntString(++m_cyclicInvCount, 8) + " " + StringHelper::toString(tagInfo.tagId) + " ";

    if (tagInfo.hasMemory)
    {
        info += " MEM@" + StringHelper::toIntString(tagInfo.memBank) + "." + StringHelper::toIntString(tagInfo.memAddr) + ":" + StringHelper::toString(tagInfo.memData) + " ";
    }

    if (tagInfo.hasApplicationInfo)
    {
        info += " APP:" + StringHelper::toString(tagInfo.applicationInfo) + " ";
    }

    print(info);
}

void TestProgram::StateChangedHandler(eRFE_CURRENT_READER_STATE newState)
{
    print("\t[E] State changed to: " + StringHelper::toString(newState));
}

void TestProgram::StatusRegisterChangedHandler(ulonglong statusRegister)
{
    print("\t[E] Status register changed " + StringHelper::toHexString(statusRegister));
}

void TestProgram::GpioValuesChangedHandler(ulong gpioValues)
{
    print("\t[E] GPIO values changed " + StringHelper::toHexString(gpioValues));
}

void TestProgram::NotificationHandler(const std::vector<byte> &payload)
{
    print("\t[E] Notification " + StringHelper::toString(payload));
}

void TestProgram::ApplicationEventHandler(const std::vector<byte> &payload)
{
    print("\t[E] ApplicationEvent " + StringHelper::toString(payload));
}
